#include "camera.h"
#include <glm/gtc/matrix_transform.hpp>


namespace vortex
{
#if 0
    bool Camera2D::on_key_pressed(EventType eventType, void* sender, void* listener, void* context)
    {
        Camera2D* camera = (Camera2D*)listener;
        bool handled = true;

        uint32_t* data = (uint32_t*)context;
        uint32_t key = data[0];
        float dx = 0.0f;
        float dy = 0.0f;
        float dzoom = 0.0f;
        float dt = 0.0f;
        switch (key)
        {
        case GLFW_KEY_LEFT:
            dx = -0.1f;
            break;
        case GLFW_KEY_RIGHT:
            dx = 0.1f;
            break;
        case GLFW_KEY_UP:
            dy = -0.1f;
            break;
        case GLFW_KEY_DOWN:
            dy = 0.1f;
            break;
        case GLFW_KEY_J:
            dt = 0.1f;
            break;
        case GLFW_KEY_K:
            dt = -0.1f;
            break;
        case GLFW_KEY_Q:
            dzoom = 0.1f;
            break;
        case GLFW_KEY_R:
            dzoom = -0.1f;
            break;
        default:
            handled = false;
            break;
        }

        camera->move(dx, dy);
        camera->rotate(dt);
        camera->zoom(dzoom);

        return handled;
    }
#endif

    Camera2D::Camera2D()
    {
        pos = glm::vec2(0.0f, 0.0f);
        rotation = 0.0;
        scale = 1.0f;
        // register_event(EventType::EVENT_KEY_PRESSED, this, on_key_pressed);
    }

    glm::mat4 Camera2D::getViewMatrix() const
    {
        glm::mat4 m(1.0f);
        m = glm::translate(m, glm::vec3(pos.x, pos.y, 0.0));
        float angle = glm::radians(rotation);
        m = glm::rotate(m, angle, glm::vec3(0.0f, 0.0f, 1.0f));
        m = glm::scale(m, glm::vec3(scale, scale, 1.0f));
        return m;
    }

    void Camera2D::zoom(float dz)
    {
        scale += dz;
        if (scale < 0.2f) scale = 0.2f;
        if (scale > 5.0f) scale = 5.0f;
    }

    void Camera2D::zoomTo(float s)
    {
        if (s > 5.0f) scale = 5.0f;
        else if (s < 0.2f) scale = 0.2f;
        else scale = s;
    }

    void Camera2D::move(float dx, float dy)
    {
        pos.x += dx;
        pos.y += dy;
    }

    void Camera2D::moveTo(float x, float y)
    {
        pos.x = x;
        pos.y = y;
    }

    void Camera2D::rotate(float dt)
    {
        rotation += dt;
    }

    void Camera2D::rotateTo(float angle)
    {
        rotation = angle;
    }

#if 0
    bool Camera3D::on_key_pressed(EventType type, void* sender, void* listener, void* context)
    {
        Camera3D* camera = (Camera3D*)listener;
        bool handled = true;

        uint32_t* data = (uint32_t*)context;
        uint32_t key = data[0];
        float dx = 0.0f;
        float dy = 0.0f;
        float dz = 0.0f;
        float dyaw = 0.0f;
        float dpitch = 0.0f;
        float sensitivity = 180 / 3.14f;
        switch (key)
        {
        case GLFW_KEY_LEFT:
            dx = -0.1f;
            break;
        case GLFW_KEY_RIGHT:
            dx = 0.1f;
            break;
        case GLFW_KEY_UP:
            dy = -0.1f;
            break;
        case GLFW_KEY_DOWN:
            dy = 0.1f;
            break;
        case GLFW_KEY_F:
            dz = 0.1f;
            break;
        case GLFW_KEY_B:
            dz = -0.1f;
            break;
        case GLFW_KEY_A:
            dyaw = 0.1f;
            break;
        case GLFW_KEY_D:
            dyaw = -0.1f;
            break;
        case GLFW_KEY_W:
            dpitch = 0.1f;
            break;
        case GLFW_KEY_S:
            dpitch = -0.1f;
            break;
        default:
            handled = false;
            break;
        }

        camera->translate(dx, dy, dz);
        dyaw *= sensitivity;
        dpitch *= sensitivity;
        camera->rotate(dyaw, dpitch);
        return handled;
    }
#endif

    Camera3D::Camera3D(const glm::vec3& pos, float yaw, float pitch)
    {
        this->worldUp = glm::vec3(0.0f, 1.0f, 0.0f);

        this->pos = pos;
        this->yaw = yaw;
        this->pitch = pitch;
        this->updateVectors();
        // register_event(EventType::EVENT_KEY_PRESSED, this, on_key_pressed);
    }

    glm::mat4 Camera3D::getViewMatrix() const
    {
        glm::vec3 front = getFrontVector();
        return glm::lookAt(pos, pos + front, up);
    }

    glm::mat4 Camera3D::getViewMatrixOrigin() const
    {
        glm::vec3 origin = glm::vec3(0.0f);
        glm::vec3 front = getFrontVector();
        return glm::lookAt(origin, origin + front, up);
    }

    void Camera3D::translate(float dx, float dy, float dz)
    {
        pos.x += dx;
        pos.y += dy;
        pos.z += dz;
    }

    void Camera3D::rotate(float dyaw, float dpitch)
    {
        yaw += dyaw;
        pitch += dpitch;
        if (pitch > 80.0f) pitch = 80.0f;
        if (pitch < -80.0f) pitch = -80.0f;
    }

    void Camera3D::moveTo(float x, float y, float z)
    {
        pos.x = x;
        pos.y = y;
        pos.z = z;
    }

    void Camera3D::pointTo(float x, float y, float z)
    {
        // TODO: debug.
        glm::vec3 front = { x - pos.x, y - pos.y, z - pos.z };
        // calculate pitch and yaw
        pitch = glm::degrees(asin(front.y));
        yaw = glm::degrees(atan(front.x / front.z));

        updateVectors();
    }

    glm::vec3 Camera3D::getFrontVector() const
    {
        glm::vec3 front;
        front.x = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
        front.y = sin(glm::radians(pitch));
        front.z = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
        return glm::normalize(front);
    }

    void Camera3D::updateVectors()
    {
        glm::vec3 front = getFrontVector();
        glm::vec3 right = glm::normalize(glm::cross(front, worldUp));
        up = glm::normalize(glm::cross(right, front));
    }

    glm::mat4 Camera3D::getProjectionMatrix(uint32_t width, uint32_t height) const
    {
        float fov = 45.0f;
        float near = 0.1f;
        float far = 100.0f;
        float ratio = width * 1.0f / height;
        return glm::perspective(fov, ratio, near, far);
    }
}
